namespace Microsoft.ContactsBridge
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ComponentModel;
    using Standard;
    using Microsoft.ContactsBridge.Interop;

    internal class LabelCollection : ILabelCollection
    {
        private Contact _contact;
        private string _node;
        private string _collection;
        private List<string> _labels;
        private bool _dirty;

        private static string _GetCollectionFromNode(string node)
        {
            Assert.IsTrue(ContactUtil.IsPropertyValidNode(node));
            return node.Substring(0, node.LastIndexOf('/'));
        }

        private static string _GetFormatFromNode(string node)
        {
            Assert.IsTrue(ContactUtil.IsPropertyValidNode(node));
            return node.Substring(0, node.LastIndexOf('[') + 1) + "{0}]";
        }

        private void _OnContactPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            ContactPropertyChangedEventArgs cpc = (ContactPropertyChangedEventArgs)e;

            switch (cpc.ChangeType)
            {
                case ContactPropertyChangeType.NodeRemoved:
                    if (cpc.PropertyName == _node)
                    {
                        // This is still valid, but re-request the label collection.
                        _dirty = true;
                    }
                    break;
                case ContactPropertyChangeType.NodePrepended:
                    if (null == _collection)
                    {
                        _collection = _GetCollectionFromNode(_node);
                    }
                    if (0 == string.Compare(_collection, _GetCollectionFromNode(cpc.PropertyName), StringComparison.InvariantCultureIgnoreCase))
                    {
                        // Prepending a node into this collection, so just increment this index by one.
                        // The plus 2 is because GetIndexFromNode is 0 based and the XML node is 1 based
                        // (still only incrementing it by one).
                        _node = string.Format(null, _GetFormatFromNode(_node), ContactUtil.GetIndexFromNode(_node) + 2);
                        Assert.IsTrue(ContactUtil.IsPropertyValidNode(_node));
                        _dirty = true;
                    }
                    break;
            }

        }

        public LabelCollection(Contact contact, string nodeName)
        {
            Assert.IsNotNull(contact);
            Assert.IsNotNull(nodeName);
            // Verify that the string is a valid node.
            if (!ContactUtil.IsPropertyValidNode(nodeName))
            {
                throw new SchemaException(string.Format(null, "The arrayNode {0} doesn't appear to be valid", nodeName));
            }

            _contact = contact;
            _node = nodeName;

            // Validate the argument
            if (!ContactUtil.DoesPropertyExist((IContactProperties)_contact.IContact, _node))
            {
                throw new PropertyNotFoundException(string.Format(null, "The arrayNode {0} doesn't exist in this contact.", nodeName));
            }

            // Load _labels on demand.

            // Use this to know whether _labels needs to be reloaded.
            _dirty = true;
            _contact.PropertyChanged += _OnContactPropertyChanged;
        }

        private void _RefreshLabels()
        {
            if (_dirty)
            {
                ContactUtil.GetLabels((IContactProperties)_contact.IContact, _node, out _labels)
                    .ThrowIfFailed("Unable to get the labels for the arrayNode " + _node);
                _dirty = false;
            }
        }

        #region ICollection<string> Members

        void ICollection<string>.Add(string item)
        {
            Add(item);
        }

        public void Clear()
        {
            _dirty = true;
            ContactUtil.DeleteLabels((IContactProperties)_contact.IContact, _node)
                .ThrowIfFailed("Unable to clear the properties on this conact.");

            // This should have a listener on the contact, so it really shouldn't be NULL.
            _contact.NotifyPropertyChanged(
                this,
                new ContactPropertyChangedEventArgs(
                    _node,
                    ContactPropertyChangeType.LabelsRemoved));
        }

        public bool Contains(string item)
        {
            Validate.NotNull(item, "item");
            _RefreshLabels();
            return _labels.Exists(delegate(string s) { return 0 == string.Compare(s, item, true); });
        }

        public void CopyTo(string[] array, int arrayIndex)
        {
            _RefreshLabels();
            _labels.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get
            {
                _RefreshLabels();
                return _labels.Count;
            }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(string item)
        {
            Validate.NotNull(item, "item");
            _RefreshLabels();
            if (0 == _labels.RemoveAll(delegate(string s) { return 0 == string.Compare(s, item, true); }))
            {
                // The item doesn't need to be removed from the collection.
                return false;
            }
            _dirty = true;

            try
            {
                ContactUtil.DeleteLabels((IContactProperties)_contact.IContact, _node)
                    .ThrowIfFailed("Unable to modify the labels on the arrayNode " + _node);

                ContactUtil.SetLabels((IContactProperties)_contact.IContact, _node, _labels)
                    .ThrowIfFailed("Unable to modify the labels on the arrayNode " + _node);
            }
            catch
            {
                _contact.Invalidate();
                throw;
            }

            // This should have a listener on the contact, so it really shouldn't be NULL.
            _contact.NotifyPropertyChanged(
                this,
                new ContactPropertyChangedEventArgs(
                    _node,
                    ContactPropertyChangeType.LabelsRemoved));

            return true;
        }

        #endregion

        #region IEnumerable<string> Members

        public IEnumerator<string> GetEnumerator()
        {
            _RefreshLabels();
            return _labels.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region ILabelCollection Members
        public string PropertyName
        {
            get
            {
                return _node;
            }
        }

        public bool Add(string item)
        {
            // Don't add duplicate labels to the contact.
            if (Contains(item))
            {
                return false;
            }

            _dirty = true;
            ContactUtil.SetLabels((IContactProperties)_contact.IContact, _node, new string[] { item })
                .ThrowIfFailed("Unable to add the label to the contact.");

            _contact.NotifyPropertyChanged(
                this,
                new ContactPropertyChangedEventArgs(
                    _node,
                    ContactPropertyChangeType.LabelsAdded));
            return true;
        }

        public bool AddRange(params string[] items)
        {
            Validate.NotNull(items, "items");

            bool changed = false;
            foreach (string label in items)
            {
                if (Add(label))
                {
                    changed = true;
                }
            }
            return changed;
        }
        #endregion
    }

}
